package com.franklin.java.completion.map.mybatis;

import com.franklin.java.api.mybatis.annotation.MyBatisAnnotation;
import com.franklin.java.api.mybatis.annotation.MyBatisAnnotations;
import com.franklin.java.api.mybatis.dom.franklin.MyMapper;
import com.franklin.java.api.mybatis.dom.franklin.MySelect;
import com.franklin.java.api.mybatis.dom.utils.MapperUtils;
import com.franklin.java.completion.map.MapData;
import com.franklin.java.entity.sql.ColumnInfo;
import com.franklin.java.service.database.DataSourceServiceSupport;
import com.franklin.java.service.database.DatabaseHandler;
import com.franklin.java.utils.PsiUtils;
import com.franklin.java.utils.XmlTagUtils;
import com.franklin.plugins.common.utils.DataCheckUtils;
import com.franklin.plugins.common.utils.PatternUtils;
import com.franklin.plugins.common.utils.StringHelper;
import com.intellij.openapi.project.Project;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.xml.*;
import org.jetbrains.annotations.NotNull;

import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.Optional;

/**
 * Mybatis Mapper 方法处理器
 *
 * @author Franklin
 * @since 2021/08/21 23:10
 */
public class MapperMethodHandler extends MybatisMethodHandler<PsiMethod> {

    public MapperMethodHandler(Project myProject) {
        super(myProject);
    }

    @Override
    public List<MapData> resolveMethod(PsiMethod psiMethod) {
        List<ColumnInfo> columnInfos = resolveColumnInfo(psiMethod);
        return this.sqlHelper.convertColumnToMapData(columnInfos);
    }

    @Override
    public List<ColumnInfo> resolveColumnInfo(PsiMethod psiMethod) {
        String mybatisSql = findSelectMapperMethod(psiMethod);
        if (DataCheckUtils.isEmpty(mybatisSql)){
            return Collections.emptyList();
        }

        mybatisSql = StringHelper.fillElExpressionAll(mybatisSql, "'franklin'");
        DatabaseHandler databaseHandler = DataSourceServiceSupport.getInstance()
            .getDatabaseHandler(psiMethod);
        List<ColumnInfo> columns = this.sqlHelper.getColumns(databaseHandler.getDbType(), mybatisSql);
        databaseHandler.resolveColumnInfo(columns);
        return columns;
    }

    private String findRefSql(String path, String refMapper, String refSqlId) {
        MyMapper mapper = MapperUtils.findMapper(myProject,refMapper, path);
        return Optional.ofNullable(mapper)
            .map(myMapper -> myMapper.getSql(refSqlId))
            .map(sql -> resolveSubTag(sql,mapper))
            .orElse("");

    }

    private String resolveMapperSql(XmlTag sqlTag, MyMapper nowMapper) {
        if (Objects.isNull(sqlTag)) {
            return "";
        }
        return Optional.ofNullable(sqlTag)
            .map(xmlTag -> resolveSubTag(xmlTag, nowMapper))
            .orElse("");
    }

    private  String resolveSubTag(@NotNull XmlTag xmlTag, MyMapper nowMapper) {
        String tagName = xmlTag.getName();
        StringBuilder stringBuilder = new StringBuilder();
        List<XmlElement> xmlElementList = XmlTagUtils.findXmlChild(xmlTag);
        String childValue = resolveChildrenTag(xmlElementList, nowMapper);
        if (tagName.equals("where")) {
            if (DataCheckUtils.isNotEmpty(childValue)) {
                StringBuilder whereFirstKeywordBuilder = new StringBuilder();
                for (int i = 0; i < childValue.length(); i++) {
                    char c = childValue.charAt(i);
                    if (c == ' ') {
                        break;
                    }
                    whereFirstKeywordBuilder.append(c);
                }
                childValue = childValue.replaceFirst(whereFirstKeywordBuilder.toString(), "");
            }
            stringBuilder.append("where").append(' ').append(childValue);
        } else if (tagName.equals("include")) {
            String refid = Optional
                .ofNullable(xmlTag.getAttribute("refid"))
                .map(XmlAttribute::getValue).orElse("");
            if (refid.contains(".")) {
                int lastDotIndex = refid.lastIndexOf(".");
                String refMapper = refid.substring(0, lastDotIndex);
                String refSqlId = refid.substring(lastDotIndex + 1);
                String refSql = findRefSql(xmlTag.getContainingFile().getVirtualFile().getPath(), refMapper, refSqlId);
                childValue = refSql;
            } else {
                //当前Mapper的SQL引用
                XmlTag sqlTag = nowMapper.getSql(refid);
                String subSql = Optional.ofNullable(sqlTag)
                    .map(tag -> resolveSubTag(tag, nowMapper))
                    .orElse("");
                childValue = subSql;
            }

            stringBuilder.append(" ").append(childValue);
        } else if (tagName.equals("foreach")) {
            String open = Optional
                .ofNullable(xmlTag.getAttribute("open"))
                .map(XmlAttribute::getValue).orElse("");

            String close = Optional
                .ofNullable(xmlTag.getAttribute("close"))
                .map(XmlAttribute::getValue).orElse("");

            String item = Optional
                .ofNullable(xmlTag.getAttribute("item"))
                .map(XmlAttribute::getValue).orElse("");

            String index = Optional
                .ofNullable(xmlTag.getAttribute("index"))
                .map(XmlAttribute::getValue).orElse("");

            if (DataCheckUtils.isNotEmpty(item)) {
                String elExpression = PatternUtils.findElExpression(childValue, item);
                if (DataCheckUtils.isNotEmpty(elExpression)) {
                    childValue = StringHelper.fillElExpression(childValue, elExpression, "1");
                }
            }

            if (DataCheckUtils.isNotEmpty(index)) {
                String elExpression = PatternUtils.findElExpression(childValue, index);
                if (DataCheckUtils.isNotEmpty(elExpression)) {
                    childValue = StringHelper.fillElExpression(childValue, elExpression, "1");
                }
            }

            StringBuilder foreachBuilder = new StringBuilder();
            foreachBuilder.append(open);
            foreachBuilder.append(childValue);
            foreachBuilder.append(close);

            stringBuilder.append(foreachBuilder);
        } else {
            stringBuilder.append(" ").append(childValue);
        }
        return stringBuilder.toString();
    }

    private  String resolveChildrenTag(@NotNull List<XmlElement> xmlElementList, MyMapper nowMapper) {
        StringBuilder stringBuilder = new StringBuilder();
        for (XmlElement xmlElement : xmlElementList) {
            if (xmlElement instanceof XmlText) {
                XmlText xmlText = (XmlText) xmlElement;
                String xmlTextValue = xmlText.getValue();
                stringBuilder.append(xmlTextValue);
            } else {
                XmlTag xmlTag = (XmlTag) xmlElement;
                stringBuilder.append(resolveSubTag(xmlTag, nowMapper));
            }
        }
        return stringBuilder.toString().trim();
    }

    private String findSelectMapperMethod(@NotNull PsiMethod psiMethod) {
        if (Objects.nonNull(psiMethod.getBody())) {
            return "";
        }
        PsiClass containingClass = psiMethod.getContainingClass();
        if (Objects.isNull(containingClass)) {
            return "";
        }
        MyMapper mapper = MapperUtils.findMapper(psiMethod.getProject(),
            containingClass.getQualifiedName(),
            containingClass.getContainingFile().getVirtualFile().getPath()
        );
        if (Objects.isNull(mapper)) {
            return "";
        }
        //解析注解Sql
        String sqlInAnnotation = resolveSqlInAnnotation(psiMethod);
        if (DataCheckUtils.isNotEmpty(sqlInAnnotation)) {
            return sqlInAnnotation;
        }

        List<MySelect> selects = mapper.getSelects();

        for (MySelect select : selects) {
            String id = select.getId();
            if (StringHelper.equals(id, psiMethod.getName())) {
                return resolveMapperSql(select.getXmlTag(), mapper);
            }
        }
        return "";
    }

    private  String resolveSqlInAnnotation(PsiMethod psiMethod) {
        PsiAnnotation psiAnnotation = psiMethod.getAnnotation(
            MyBatisAnnotations.get(MyBatisAnnotations.Select.class).getQualifiedName()
        );
        if (Objects.nonNull(psiAnnotation)) {
            return Optional.ofNullable(MyBatisAnnotations.parse(psiAnnotation))
                .map(MyBatisAnnotation::getValue)
                .map(sql -> StringHelper.fillElExpressionAll(sql, "'franklin'"))
                .orElse("");
        }
        return "";
    }

}
